#include <stdio.h>
#include <unistd.h>
#include <dlfcn.h>
#include <string.h>
#include <stdlib.h>
#include <spawn.h>
#include <regex.h>
#include <libgen.h>

#define bool int
#define true 1
#define false 0

int (*real_execve)(const char*, char* const*, char* const*);
int (*real_posix_spawn)(pid_t*, const char*, const posix_spawn_file_actions_t*, const posix_spawnattr_t*, char* const*, char* const*);
int (*real_posix_spawnp)(pid_t*, const char*, const posix_spawn_file_actions_t*, const posix_spawnattr_t*, char* const*, char* const*);

static bool check_env_commands(const char* target_command)
{
    const char* commands = getenv("interceptor_commands");
    if (commands == NULL) {
        return false;
    }
    char* command = strtok(commands, ",");
    while (command != NULL) {
        if (strcmp(command, target_command) == 0) {
            return true;
        }
        command = strtok(NULL, ",");
    }
    return false;
}


static bool match_regex(const char* pattern, const char* target)
{
    regex_t regex;
    int init = regcomp(&regex, pattern, REG_EXTENDED | REG_NOSUB);
    if (init != 0) {
        fprintf(stderr, "Error compiling regex: %s\n", pattern);
        exit(1);
    }
    int res = regexec(&regex, target, 0, NULL, 0);
    bool matched = false;
    if (res == REG_NOERROR) {
        matched = true;
    }
    regfree(&regex);
    return matched;
}
static bool check_c_compiler(const char* filepath)
{
    char* dup_filepath = strdup(filepath);
    char* filename = basename(dup_filepath);
    if (filename == NULL) {
        return false;
    }
    bool mathced = (match_regex("(.*-)*gcc(-\\\\d+)?", filename) ||
                    match_regex(filename, "(.*-)*clang(-\\\\d+)?"));
    free(dup_filepath);
    return mathced;
}

static bool check_filepath(const char* filepath)
{
    if (check_env_commands(filepath)) {
        return true;
    }
    return check_c_compiler(filepath);
}

static bool need_to_intercept(const char* filepath, const char** argv)
{
    static char absolute_path[1024];
    if (realpath(filepath, absolute_path) != NULL) {
        return check_filepath(absolute_path);
    } else {
        return check_filepath(filepath);
    }
}

void try_to_intercept(const char* filepath, const char** argv, const char** envp)
{
    const char* log_path = getenv("interceptor_log_path");
    if (log_path == NULL) {
        fprintf(stderr, "Warning: interceptor_log_path not set\n");
        exit(1);
    }
    if (!need_to_intercept(filepath, argv)) {
        return;
    }
    char cwd[1024];
    getcwd(cwd, sizeof(cwd));
    char buffer[1024 * 100];
    sprintf(buffer, "cd %s &&", cwd);
    for (int i = 0; argv[i] != NULL; i++) {
        strcat(buffer, " ");
        strcat(buffer, argv[i]);
    }
    FILE* log_file = fopen(log_path, "a");
    fprintf(log_file, "%s\n", buffer);
    fclose(log_file);
}

int execve(const char* filepath, char* const argv[], char* const envp[])
{
    if (real_execve == NULL) {
        real_execve = dlsym(RTLD_NEXT, "execve");
    }
    try_to_intercept(filepath, argv, envp);
    return real_execve(filepath, argv, envp);
}

int posix_spawn(pid_t* restrict pid, const char* restrict filepath,
                const posix_spawn_file_actions_t* restrict file_actions,
                const posix_spawnattr_t* restrict attrp,
                char* const argv[restrict],
                char* const envp[restrict])
{
    if (real_posix_spawn == NULL) {
        real_posix_spawn = dlsym(RTLD_NEXT, "posix_spawn");
    }
    try_to_intercept(filepath, argv, envp);
    return real_posix_spawn(pid, filepath, file_actions, attrp, argv, envp);
}

int posix_spawnp(pid_t* restrict pid, const char* restrict filepath,
                 const posix_spawn_file_actions_t* restrict file_actions,
                 const posix_spawnattr_t* restrict attrp,
                 char* const argv[restrict],
                 char* const envp[restrict])
{
    if (real_posix_spawnp == NULL) {
        real_posix_spawnp = dlsym(RTLD_NEXT, "posix_spawnp");
    }
    try_to_intercept(filepath, argv, envp);
    return real_posix_spawnp(pid, filepath, file_actions, attrp, argv, envp);
}